import { defined, Color, EllipsoidGeodesic, Cartographic } from "cesium";
import { ClippingPlaneCollection, ClippingPlane, Plane } from "cesium";
import { Cartesian2, Cartesian3, OrientedBoundingBox } from "cesium";
import { Transforms, Matrix4 } from "cesium";
import { MyCesiumConsts } from "@/constant";

function createClippingBoxInitPlanes(isAccessible = false, showEdge = false) {
    // the creating order is: front, end, left, right, top, bottom
    // the scene will be rendered when it is at the same side of normal vector
    // e.g., the front planes has the normal vector towards opposite side
    var sidePlanes = [];
    var basePlanes = [];
    var showEdgeOptions = {};

    sidePlanes = getNormalizedSidePlanes();
    // if it is the inner-feature tileset
    if (isAccessible) {
        basePlanes = getNormalizedBasePlanes();
    }
    if (showEdge) {
        showEdgeOptions = debugShowClippingPlanesEdge();
    }
    return new ClippingPlaneCollection({
        planes: [...sidePlanes, ...basePlanes],
        unionClippingRegions: true,
        ...showEdgeOptions
    });
}

function adjustClippingBox2SurfaceLevel(tileset, isAccessible) {
    // adjust init clipping planes to show all the target tileset
    if (defined(tileset)) {
        var clippingPlanes = tileset.clippingPlanes;
        const planeArrLen = clippingPlanes.length;
        var lastPlaneIndex;
        // init the clipping planes of inner models by 6
        if (planeArrLen === MyCesiumConsts.CLIPPING_PLANE_NUM.INNER_FEATURE_NUM && isAccessible) {
            lastPlaneIndex = MyCesiumConsts.CLIPPING_PLANE_NUM.GLOBE_SURFACE_NUM;
        } else {
            lastPlaneIndex = planeArrLen - 1;
        }
        // compute corners
        const corners = calcRootBoundingVolumeCorners(tileset);
        // compute half width and length of bounding volume
        const halfs = calcCertainScaleWLHFromCorners(corners, 0.5);
        // move clipping planes which are vertical with globe surface
        var i = 0;
        for (let j = 0; i < lastPlaneIndex && j < halfs.length; i += 2, j++) {
            const halfVal = halfs[j];
            clippingPlanes.get(i).distance = halfVal;
            clippingPlanes.get(i + 1).distance = halfVal;
        }
        // move clipping planes which are horizontal with globe surface
        if (i < planeArrLen) {
            // only need to move the top plane
            clippingPlanes.get(i).distance = halfs[2] * 2;
        }
        return true;
    }
}

// function adjustClippingBoxAroundCamera(tileset, cameraPos = new Cartesian3()) {
//     // used when visual level is FEATURE_FOCUS or INNER_FEATURE
//     // it is hard to create clipping box on a feature in building complex
//     if (defined(tileset) && defined(cameraPos)) {
//         const tilesetCenterArr = cartesian2Array(tileset.boundingSphere.center);
//         const cameraPositionArr = cartesian2Array(cameraPos);
//         const clippingPlanes = tileset.clippingPlanes;
//         const planeCount = clippingPlanes.length;
//         const cameraVisibility = Math.abs(MyCesiumConsts.CAMERA_VISIBILITY);
//         if (planeCount !== 0 && MyCesiumConsts.CLIPPING_PLANE_NUM.checkNum(planeCount)) {
//             var diffDistance = 0;
//             for (let i = 0, j = -1; i < planeCount; i++) {
//                 var prePlane = clippingPlanes.get(i);
//                 console.log(prePlane.distance);
//                 if (i % 2 === 0) {
//                     // According to the defined order of planes
//                     // change the axis every two times
//                     j++;
//                     diffDistance = tilesetCenterArr[j] - cameraPositionArr[j];
//                     console.log(diffDistance);
//                 } else {
//                     // turn into opposite distance with direction every two times
//                     diffDistance = -diffDistance;
//                 }
//                 if (i === 2) {
//                     diffDistance = correctImageryOffset(diffDistance);
//                 }
//                 // correct final distance with visible distance to simulate real situation
//                 prePlane.distance = diffDistance + cameraVisibility;
//             }
//         }
//     }
// }

function adjustClippingBox2FeatureFocus(parentTileset, childTileset) {
    // used when visual level is FEATURE_FOCUS
    // moving the parent clipping box to child bounding volume
    // child bounding volume as a moving standard
    if (defined(parentTileset) && defined(childTileset)) {
        const parentTilesetCartographic = Cartographic.fromCartesian(parentTileset.boundingSphere.center);
        const childTilesetCartographic = Cartographic.fromCartesian(childTileset.boundingSphere.center);
        // calc different distance on longitude, latitude and height
        const axisDistanceObj = calcSurfaceAxisDistances(parentTilesetCartographic, childTilesetCartographic);
        const parentClippingPlanes = parentTileset.clippingPlanes;
        const childClippingPlanes = childTileset.clippingPlanes;
        const planeCount = parentClippingPlanes.length;
        const aroundVisibility = Math.abs(MyCesiumConsts.FOCUS_AROUND_VISIBILITY);
        if (planeCount !== 0 && MyCesiumConsts.CLIPPING_PLANE_NUM.checkNum(planeCount)) {
            var diffDistance;
            var visibility;
            // loop parent clipping planes
            for (let i = 0, j = -1; i < planeCount; i++) {
                var preParentPlane = parentClippingPlanes.get(i);
                // use FRONT, LEFT plane as reference, and the others use negative value
                if (i % 2 === 0) {
                    // every two times to update distance standard and child plane distance
                    // use FRONT, LEFT plane of child tileset as reference to get the present child plane distance
                    const preChildPlane = childClippingPlanes.get(i);
                    const childDistance = Math.abs(preChildPlane.distance);
                    visibility = childDistance + aroundVisibility;

                    j++;
                    if (axisDistanceObj.relations[j]) {
                        diffDistance = -axisDistanceObj.diffDistances[j];
                    } else {
                        diffDistance = axisDistanceObj.diffDistances[j];
                    }
                } else {
                    // turn into opposite distance with direction every two times
                    diffDistance = -diffDistance;
                }
                // correct final distance with visible distance to simulate real situation
                preParentPlane.distance = diffDistance + visibility;
            }
        }
    }
}

function adjustClippingBoxAroundCamera(tileset, cameraPos, floorHeight, cameraVisibility = Math.abs(MyCesiumConsts.CAMERA_VISIBILITY)) {
    // used when visual level is INNER_FEATURE
    // it is hard to create clipping box on a feature in building complex
    if (defined(tileset) && defined(cameraPos)) {
        const tilesetCenterCartographic = Cartographic.fromCartesian(tileset.boundingSphere.center);
        const cameraPositionCartographic = Cartographic.fromCartesian(cameraPos);
        // calc different distance on longitude, latitude and height
        const axisDistanceObj = calcSurfaceAxisDistances(tilesetCenterCartographic, cameraPositionCartographic);
        const clippingPlanes = tileset.clippingPlanes;
        const planeCount = clippingPlanes.length;
        if (planeCount !== 0 && MyCesiumConsts.CLIPPING_PLANE_NUM.checkNum(planeCount)) {
            var diffDistance = 0;
            for (let i = 0, j = -1; i < 4; i++) {
                var prePlane = clippingPlanes.get(i);
                // use FRONT, LEFT and TOP plane as reference, and the others use negative value
                if (i % 2 === 0) {
                    // According to the defined order of planes
                    // change the axis every two times
                    j++;
                    // diffDistance = tilesetCenterArr[j] - cameraPositionArr[j];
                    if (axisDistanceObj.relations[j]) {
                        diffDistance = -axisDistanceObj.diffDistances[j];
                    } else {
                        diffDistance = axisDistanceObj.diffDistances[j];
                    }
                    console.log(diffDistance);
                } else {
                    // turn into opposite distance with direction every two times
                    diffDistance = -diffDistance;
                }
                // correct final distance with visible distance to simulate real situation
                prePlane.distance = diffDistance + cameraVisibility;
            }
            // specially coping with the top and bottom clipping planes
            if (planeCount === MyCesiumConsts.CLIPPING_PLANE_NUM.INNER_FEATURE_NUM) {
                const cameraHeight = cameraPositionCartographic.height;
                console.log(`height: ${cameraHeight}`);
                if (!floorHeight || typeof floorHeight !== 'number' || floorHeight < 1) {
                    floorHeight = MyCesiumConsts.DEFAULT_AVERAGE_FLOOR_HEIGHT;
                }
                clippingPlanes.get(4).distance = cameraHeight + floorHeight;
                clippingPlanes.get(5).distance = -cameraHeight + floorHeight;
            }
        }
    }
}

function debugShowClippingBox(tileset) {
    const viewer = window.viewer.viewerInstance;
    if (defined(viewer) && defined(tileset)) {
        const clippingPlanes = tileset.clippingPlanes;
        // get corners after transform
        const corners = calcRootBoundingVolumeCorners(tileset);
        $.each(corners, (index, corner) => {
            viewer.entities.add({
                position: corner,
                point: {
                    pixelSize: 5,
                    color: Color.BLACK
                }
            })
        });
        // get center pointer on every plane of box
        const centerPoints = calcCenterPointOnEveryPlaneOfBox(corners);
        // get width, length and height of root bounding volume
        const whfArr = calcCertainScaleWLHFromCorners(corners, 1);
        const basePlanes = compositePlanesByWLH(...whfArr);
        const normalPlanes = [
            new Plane(Cartesian3.UNIT_X, 0.0),
            new Plane(Cartesian3.UNIT_Y, 0.0)
        ];

        // create plane entities
        var baseModelName = '';
        const modelName = tileset.modelName;
        if (modelName && typeof modelName === 'string' && modelName.length !== 0) {
            baseModelName = modelName;
        } else {
            var url = tileset.basePath;
            url = url.substring(0, url.length - 1);
            baseModelName = url.substring(url.lastIndexOf('/') + 1, url.length);
        }
        const lastPlaneIndex = clippingPlanes.length - 1;
        for (let i = 0, j = 0; i < lastPlaneIndex && j < basePlanes.length; j++) {
            var count = 0;
            while (count++ < 2) {
                viewer.entities.add({
                    // id: baseModelName + '_' + MyCesiumConsts.PLANE_NAME_OF_BOX[i],
                    position: centerPoints[i],
                    plane: {
                        dimensions: basePlanes[j],
                        material: Color.WHITE.withAlpha(0.3),
                        outline: true,
                        outlineColor: Color.WHITE,
                        plane: normalPlanes[j]
                    }
                });
                i++;
            }
        }
    }
}

function getNormalizedSidePlanes() {
    // the creating order is: front, end, left, right
    return [
        new ClippingPlane(new Cartesian3(-1.0, 0.0, 0.0), 0.0),
        new ClippingPlane(Cartesian3.UNIT_X, 0.0),
        new ClippingPlane(new Cartesian3(0.0, -1.0, 0.0), 0.0),
        new ClippingPlane(Cartesian3.UNIT_Y, 0.0),
    ];
}

function getNormalizedBasePlanes() {
    // the creating order is: top, bottom
    return [
        new ClippingPlane(new Cartesian3(0.0, 0.0, -1.0), 0.0),
        new ClippingPlane(Cartesian3.UNIT_Z, 0.0)
    ];
}

function debugShowClippingPlanesEdge() {
    return {
        edgeWidth: 2.0,
        edgeColor: Color.RED
    };
}

function calcRootBoundingVolumeCorners(tileset) {
    // get a OrientedBoundingBox object
    const rootBoundingVolume = tileset.root.boundingVolume.boundingVolume;
    // compute 8 corners of the bounding volume cube
    const originalCorners =  OrientedBoundingBox.computeCorners(rootBoundingVolume);
    var newCorners = [];
    for (let i = 0; i < originalCorners.length; i++) {
        const corner = originalCorners[i];
        const newPosition = translate(corner, -1484.0, -1338.0, 0);
        newCorners.push(newPosition);
    }
    return newCorners;
}

function calcCertainScaleWLHFromCorners(corners, scale = 1.0) {
    return [
        Cartesian3.distance(corners[1], corners[5]) * scale,
        Cartesian3.distance(corners[1], corners[3]) * scale,        
        Cartesian3.distance(corners[0], corners[1]) * scale
    ];
}

function calcCenterPointOnEveryPlaneOfBox(corners) {
    // the order is: front, end, left, right, top, bottom
    return [
        Cartesian3.lerp(corners[5], corners[6], 0.5, new Cartesian3()),
        Cartesian3.lerp(corners[0], corners[3], 0.5, new Cartesian3()),
        Cartesian3.lerp(corners[2], corners[7], 0.5, new Cartesian3()),
        Cartesian3.lerp(corners[0], corners[5], 0.5, new Cartesian3()),
        Cartesian3.lerp(corners[3], corners[5], 0.5, new Cartesian3()),
        Cartesian3.lerp(corners[0], corners[6], 0.5, new Cartesian3()),
    ];
}

function compositePlanesByWLH(width, length, height) {
    // the order is: yz, xz, xy
    return [
        new Cartesian2(length, height),
        new Cartesian2(width, height),
        new Cartesian2(width, length),
    ]
}

function translate(point = Cartesian3.ZERO, ...transVal) {
    const transforms = Transforms.eastNorthUpToFixedFrame(point);
    const translations = Matrix4.setTranslation(Matrix4.IDENTITY, Cartesian3.fromArray(transVal), new Matrix4());
    const modelMatrix = Matrix4.multiply(transforms, translations, new Matrix4());
    const newPosition = Matrix4.getTranslation(modelMatrix, new Cartesian3());
    return newPosition;
}

function calcSurfaceAxisDistances(point1 = new Cartographic(), point2 = new Cartographic()) {
    var longitudeDiffDistance = 0;
    var latitudeDiffDistance = 0;
    var heightDiffDistance = 0;
    var tempPoint;
    const geodesic = new EllipsoidGeodesic();
    // use point2 as the reference, point1 move to point2
    // move the latitude to the same, calc longitude difference
    tempPoint = new Cartographic(point1.longitude, point2.latitude, point2.height);
    geodesic.setEndPoints(tempPoint, point2);
    longitudeDiffDistance = geodesic.surfaceDistance;

    // move the longitude to the same, calc latitude difference
    tempPoint = new Cartographic(point2.longitude, point1.latitude, point2.height);
    geodesic.setEndPoints(tempPoint, point2);
    latitudeDiffDistance = geodesic.surfaceDistance;

    // move the longitude, latitude to the same, calc height difference
    tempPoint = new Cartographic(point2.longitude, point2.latitude, point1.height);
    geodesic.setEndPoints(tempPoint, point2);
    heightDiffDistance = geodesic.surfaceDistance;

    return {
        diffDistances: [longitudeDiffDistance, latitudeDiffDistance, heightDiffDistance],
        relations: certainPositionalRelationOfCartographic(point1, point2),
    }
}

function certainPositionalRelationOfCartographic(point1 = new Cartographic(), point2 = new Cartographic()) {
    return [
        point1.longitude - point2.longitude >= 0,
        point1.latitude - point2.latitude >= 0,
        point1.height - point2.height >= 0,
    ]
}

export default {
    createClippingBoxInitPlanes,
    debugShowClippingBox,
    adjustClippingBox2SurfaceLevel,
    adjustClippingBoxAroundCamera,
    adjustClippingBox2FeatureFocus,
}